
/*========================================================================================
 *                                                                                      *
 *               Copyright(c) 2013-2014, Cha SangWoo, All rights reserved.              *
 *                                                                                      *
 * This file is part of ChaOS.                                                          *
 *                                                                                      *
 * Permission to use, copy, modify, and distribute this software in source and binary   *
 * forms and its documentation for any purpose and without fee is hereby granted,       *
 * provided that the above copyright notice appear in all copies and that both that     *
 * copyright notice and this permission notice appear in supporting documentation.      *
 *                                                                                      *
 * THIS SOFTWARE IS PROVIDED BY THE CHA SANGWOO AND CONTRIBUTORS "AS IS" AND ANY        *
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED            *
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   *
 * IN NO EVENT SHALL CHA SANGWOO OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,    *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT         *
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,          *
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF            *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE      *
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,                    *
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
 *                                                                                      *
========================================================================================*/

#include "co_core.h"
#include "co_state_engine.h"
#include "co_queue.h"
#include "co_sw_timer.h"

#if (FLAG_USE_CHAOS_STATE_ENGINE == CO_ENABLE)

#ifdef __cplusplus
    namespace ChaOS {
#endif

//////////////////////////////////////  < BEGIN >  ///////////////////////////////////////


#define PUSH_STATE(pcb)                                                                  \
        {                                                                                \
            CO_ASSERT(((pcb)->stack_pos+1) < SIZE_CHAOS_STATE_ENGINE_STACK);             \
            (pcb)->stack[((pcb)->stack_pos++)] = (pcb)->cur_state;                       \
        }

#define POP_STATE(pcb)                                                                   \
        {                                                                                \
            CO_ASSERT(((pcb)->stack_pos-1) >= 0);                                        \
            (pcb)->cur_state = (pcb)->stack[--((pcb)->stack_pos)];                       \
        }


#define CALL_STATE(pstate, proc, type, data)                                             \
                  (((STATE_PROC)(proc))((pstate)->puser_data, (type), (data)))

/*======================================================================================*/
/*!
    @fn             OBJECT co_state_engine_create(VOID)

    @brief          

    @return         Object for state engine
*/
/*======================================================================================*/
OBJECT co_state_engine_create(OBJECT  objQueue, VOID   *puser_data)
{
    STATE_ENGINE_CB    *pstate;
    UINT                size;

    size = sizeof(STATE_ENGINE_CB) - sizeof(UINT32);

    if (objQueue)
        size += co_queue_get_item_size(objQueue);

    // allocate heap memory
    pstate = (STATE_ENGINE_CB*) co_heap_alloc(size);
    CO_ASSERT(pstate != NULL);

    // set object ID
    OBJID_SET(pstate, OBJ_STATE_ID);

    // setup
    pstate->objQueue   = objQueue;
    pstate->cur_state  = (STATE) NULL;
    pstate->puser_data = puser_data;

    pstate->call_count = 0;
    pstate->stack_pos  = 0;
    pstate->stack[0]   = (STATE)0;
    pstate->stack[1]   = (STATE)0;

    return (OBJECT) pstate;
}

/*======================================================================================*/
/*
    @fn             VOID    co_state_engine_set_name(OBJECT object, CHAR *pname)

    @brief          

    @param [in]     object - 
    @param [in]     pname - 

    @return         None.
*/
/*======================================================================================*/
#if (FLAG_USE_CHAOS_STATE_ENGINE_NAME == CO_ENABLE)
VOID co_state_engine_set_name(OBJECT object, CHAR *pname)
{
    UINT        i;
    STATE_ENGINE_CB    *pstate = (STATE_ENGINE_CB*) object;

    // check object ID
    OBJID_CHECK(pstate, OBJ_STATE_ID);

    // check parameter
    CO_ASSERT(pname != NULL);

    // copy name
    for (i = 0; i < (NAME_LENGTH-1); i++)
    {
        pstate->name[i] = *pname++;

        if (pname == NULL)
            break;
    }

    // make sure that NULL terminate 
    pstate->name[NAME_LENGTH-1] = 0x00;
}
#endif


/*======================================================================================*/
/*!
    @fn             VOID co_state_engine_start(OBJECT object, STATE_PROC exit_state)

    @brief          

    @param [in]     object     - Object for state engine
    @param [in]     exit_state - 

    @return         None
*/
/*======================================================================================*/
VOID co_state_engine_start(OBJECT object, STATE_PROC entry_state)
{
    STATE               new_state;
    STATE_ENGINE_CB    *pstate = (STATE_ENGINE_CB*) object;

    // check object ID
    OBJID_CHECK(pstate, OBJ_STATE_ID);

    pstate->call_count++;

    pstate->cur_state = (STATE) entry_state;

    while (1)
    {
        new_state = (STATE)0;

        // call state to inform that it starts state.
        new_state = CALL_STATE(pstate, pstate->cur_state, TYPE_START, NULL);
        OBJID_CHECK(pstate, OBJ_STATE_ID);

        while (!new_state)
        {
            // wait queue message
            if (co_queue_recv(pstate->objQueue, (VOID*)&(pstate->message), WAIT_FOREVER) != CO_OK_SUCCESS)
            {
                CO_ASSERT_MSG(0, "state engine queue receive error");
            }

            // call state to inform that it receive message.
            new_state = CALL_STATE(pstate,
                                   pstate->cur_state,
                                   pstate->message.Command,
                                   pstate->message.pData);
            OBJID_CHECK(pstate, OBJ_STATE_ID);

        }

        if (new_state == CALL_RETURN)
        {
            pstate->call_count--;

            CO_ASSERT(pstate->call_count >= 1);

            // call state to inform that is exits state.
            new_state = CALL_STATE(pstate, pstate->cur_state, TYPE_EXIT, NULL);
            OBJID_CHECK(pstate, OBJ_STATE_ID);

            return;
        }

        // call state to inform that it exits state.
        CALL_STATE(pstate, pstate->cur_state, TYPE_EXIT, NULL);

        // change state to new one
        if (new_state)
            pstate->cur_state = new_state;

    }
}


/*======================================================================================*/
/*!
    @fn             VOID co_state_engine_call_state(OBJECT object, STATE_PROC entry_state)

    @brief          

    @param [in]     object     - Object for state engine
    @param [in]     exit_state - 

    @return         None
*/
/*======================================================================================*/
VOID co_state_engine_call_state(OBJECT object, STATE_PROC entry_state)
{
    // check object ID
    OBJID_CHECK((STATE_ENGINE_CB*) object, OBJ_STATE_ID);

    PUSH_STATE((STATE_ENGINE_CB*) object);

    co_state_engine_start(object, entry_state);

    POP_STATE((STATE_ENGINE_CB*) object);
}

/*======================================================================================*/
/*!
    @fn             BOOL co_state_engine_timer_proc(UINT16 id, 
                                                    STATE_ENGINE_TIMER_CB *pstate_timer)

    @brief          

    @param [in]     id - 
    @param [in]     pstate_timer - 

    @return         None
*/
/*======================================================================================*/
BOOL co_state_engine_timer_proc(UINT16 id, STATE_ENGINE_TIMER_CB *pstate_timer)
{
    UINT    message;

    if (pstate_timer->pstate->objQueue)
    {
        message  = (UINT)(TYPE_TIMEOUT << 16);
        message |= (id & 0xFF);
        if (co_queue_send(pstate_timer->pstate->objQueue, &message, 0) != CO_OK_SUCCESS)
            CO_ASSERT_MSG(0, "queue is full in state timer");
    }

    return TRUE;
}

/*======================================================================================*/
/*!
    @fn             OBJECT co_state_engine_timer_create(OBJECT state_object)

    @brief          

    @param [in]     state_object - 

    @return         None
*/
/*======================================================================================*/
OBJECT co_state_engine_timer_create(OBJECT state_object)
{
    STATE_ENGINE_TIMER_CB   *pstate_timer;

    // check object ID
    OBJID_CHECK((STATE_ENGINE_CB*)state_object, OBJ_STATE_ID);

    // allocate heap memory
    pstate_timer = (STATE_ENGINE_TIMER_CB*) co_heap_alloc(sizeof(STATE_ENGINE_TIMER_CB));
    CO_ASSERT(pstate_timer);

    // create software timer
    pstate_timer->timer = co_timer_create();
    CO_ASSERT(pstate_timer->timer);

        // set object ID
    OBJID_SET(pstate_timer, OBJ_STATE_TIMER_ID);

    pstate_timer->pstate = (STATE_ENGINE_CB*) state_object;

    co_timer_set_procedure(pstate_timer->timer, 
                           (TIMER_PROC)co_state_engine_timer_proc,
                           (VOID*)pstate_timer);

    return (OBJECT) pstate_timer;
}

/*======================================================================================*/
/*!
    @fn             VOID co_state_engine_timer_start(OBJECT timer_object, 
                                                     UINT16 id, 
                                                     UINT16 timeout, 
                                                     BOOL repeate)

    @brief          

    @param [in]     timer_object
    @param [in]     id
    @param [in]     timeout
    @param [in]     repeate

    @return         None
*/
/*======================================================================================*/
VOID co_state_engine_timer_start(OBJECT timer_object, 
                                 UINT16 id, 
                                 UINT16 timeout, 
                                 BOOL repeate)
{
    STATE_ENGINE_TIMER_CB   *pstate_timer = (STATE_ENGINE_TIMER_CB*) timer_object;

    // check object ID
    OBJID_CHECK(pstate_timer, OBJ_STATE_TIMER_ID);

    co_timer_start(pstate_timer->timer, id, timeout, repeate);
}


/*======================================================================================*/
/*!
    @fn             VOID co_state_engine_timer_stop(OBJECT timer_object)

    @brief          

    @param [in]     timer_object

    @return         None
*/
/*======================================================================================*/
VOID co_state_engine_timer_stop(OBJECT timer_object)
{
    STATE_ENGINE_TIMER_CB   *pstate_timer = (STATE_ENGINE_TIMER_CB*) timer_object;

    // check object ID
    OBJID_CHECK(pstate_timer, OBJ_STATE_TIMER_ID);

    co_timer_stop(pstate_timer->timer);
}

//////////////////////////////////////  <  END  >  ///////////////////////////////////////

#ifdef __cplusplus
    } /* namespace ChaOS */
#endif

#endif // (FLAG_USE_CHAOS_STATE_ENGINE == CO_ENABLE)

